package com.dysoft.flesh.gl;

import com.dysoft.bones.DataManager;
import com.dysoft.flesh.*;
import org.apache.log4j.Logger;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;
import java.nio.IntBuffer;

/**
 * @author Sean Micklethwaite
 *         Aug 16, 2010 7:38:47 PM
 */
public class GLRenderer extends AbstractRenderer implements GLEventListener, Renderer.Root {

	int w = 0, h = 0;
	final GLCanvas canvas;
	final GLRenderContext ctx;

	public GLRenderer(Renderer renderer, GLCanvas canvas) {
		super(renderer);
		this.canvas = canvas;
		this.ctx = new GLRenderContext(this, canvas.getGL());
	}

	public void init(GLAutoDrawable glAutoDrawable) {
		GL gl = glAutoDrawable.getGL();
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);  // Black Background
        gl.glClearDepth(1.0f);        // Depth Buffer Setup
        gl.glDepthFunc(GL.GL_LEQUAL);  // The Type Of Depth Testing (Less Or Equal)
        gl.glShadeModel(GL.GL_SMOOTH);      // Select Smooth Shading
        // Set Perspective Calculations To Most Accurate
        gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
        gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);    // Set The Color To White

		System.out.println("Extensions: " + gl.glGetString(GL.GL_EXTENSIONS));
		IntBuffer maxTextureUnits = IntBuffer.allocate(1);
		gl.glGetIntegerv(GL.GL_MAX_TEXTURE_IMAGE_UNITS, maxTextureUnits);
		System.out.println("Texture Units available = "+ maxTextureUnits.get(0));

		w = glAutoDrawable.getWidth();
		h = glAutoDrawable.getHeight();
	}

	public int getWidth() {
		return w;
	}
	public int getHeight() {
		return h;
	}

	@Override
	public void activate(RenderContext ctx) {
		GL gl = ((GLRenderer.GLRenderContext)ctx).getGL();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		gl.glLoadIdentity();

		super.activate(ctx);

		GLObject.runDtors(ctx);
	}

	public void deactivate(RenderContext ctx) {

	}

	public void display(GLAutoDrawable glAutoDrawable) {
		activate(ctx);
		render(ctx);
		deactivate(ctx);
	}

	protected void update(long t) {

	}

	public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int w, int h) {
		LOG.debug("!");
		GL gl = glAutoDrawable.getGL();
		GLU glu = new GLU();

		if(h <= 0) h = 1;
		final float ratio = (float)w / (float)h;
		gl.glViewport(0,0,w,h);
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(45f, ratio, 0.1, 20);
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();

		this.w = w;
		this.h = h;
	}

	public void displayChanged(GLAutoDrawable glAutoDrawable, boolean b, boolean b1) {
		LOG.debug("!");
	}


	public static class Colour implements RenderComponent {
		float [] colour;

		public Colour(float[] colour) {
			this.colour = colour;
		}

		public void activate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
			((GLRenderContext)ctx).getGL().glColor4fv(colour, 0);
		}

		public void deactivate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
		}
	}

	public static class Scale implements RenderComponent {
		float [] scale;

		public Scale(float[] scale) {
			this.scale = scale;
		}

		public void activate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
			((GLRenderContext)ctx).getGL().glScalef(scale[0], scale[1], scale[2]);
		}

		public void deactivate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
		}
	}

	public static class Translate implements RenderComponent {
		float [] v;

		public Translate(float[] v) {
			this.v = v;
		}

		public void activate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
			((GLRenderContext)ctx).getGL().glTranslatef(v[0], v[1], v[2]);
		}

		public void deactivate(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
		}
	}

	public static class Blend implements Renderer {
		final Renderer renderer;

		public Blend(Renderer renderer) {
			this.renderer = renderer;
		}

		public void render(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
			GL gl = ((GLRenderContext)ctx).getGL();
			boolean enabled = gl.glIsEnabled(GL.GL_BLEND);
			if(!enabled) gl.glEnable(GL.GL_BLEND);

			renderer.render(ctx);

			if(!enabled) gl.glDisable(GL.GL_BLEND);
		}
	}

	public static class Clear implements Renderer {
		final Renderer renderer;

		public Clear(Renderer renderer) {
			this.renderer = renderer;
		}

		public void render(RenderContext ctx) throws DataManager.DataObject.NotPresentException {
			GL gl = ((GLRenderContext)ctx).getGL();
			gl.glClear(GL.GL_COLOR_BUFFER_BIT + GL.GL_DEPTH_BUFFER_BIT);
			renderer.render(ctx);
		}
	}

	@Override
	public RenderContext getContext() {
		return ctx;
	}


	/**
	 * @author Sean Micklethwaite
	 *         22-Aug-2010
	 */
	public static class GLRenderContext extends AbstractRenderContext<GLRenderer> {
		final GL gl;
		final float [] glMatrix = new float [16];

		public GLRenderContext(GLRenderer renderer, GL gl) {
			super(renderer);
			this.gl = gl;
		}

		@Override
		public void pushTransform() {
			super.pushTransform();
			gl.glPushMatrix();
		}

		@Override
		public void popTransform() {
			gl.glPopMatrix();
			super.popTransform();
		}

		@Override
		public void translate(int[] v) {
			if(v.length == 2) {
				gl.glTranslatef((float)v[0], (float)v[1], 0f);
			} else {
				gl.glTranslatef((float)v[0], (float)v[1], (float)v[2]);
			}
		}

		@Override
		public void translate(Vector3f v) {
			gl.glTranslatef(v.x, v.y, v.z);
		}

		@Override
		public void transform(Matrix4f m) {
			// Matrix4f is row-major, GL is column-major
			glMatrix[0] = m.m00;
			glMatrix[1] = m.m10;
			glMatrix[2] = m.m20;
			glMatrix[3] = m.m30;

			glMatrix[4] = m.m01;
			glMatrix[5] = m.m11;
			glMatrix[6] = m.m21;
			glMatrix[7] = m.m31;

			glMatrix[8] = m.m02;
			glMatrix[9] = m.m12;
			glMatrix[10] = m.m22;
			glMatrix[11] = m.m32;

			glMatrix[12] = m.m03;
			glMatrix[13] = m.m13;
			glMatrix[14] = m.m23;
			glMatrix[15] = m.m33;

			gl.glMultMatrixf(glMatrix, 0);
		}

		@Override
		public void scale(float[] v) {
			gl.glScalef(v[0], v[1], v[2]);
		}

		@Override
		public void drawRect(int[] topLeft, int[] topRight) {

		}

		public GL getGL() {
			return gl;
		}
	}
}
